package com.gdteam.kernel.event.impl;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.tapestry5.ioc.annotations.EagerLoad;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.scannotation.AnnotationDB;
import org.scannotation.ClasspathUrlFinder;
import org.slf4j.Logger;

import com.gdteam.kernel.Operations;
import com.gdteam.kernel.event.Event;
import com.gdteam.kernel.event.EventManager;
import com.gdteam.kernel.event.annotation.OnEvent;
import com.gdteam.kernel.main.ServiceManager;

@EagerLoad
public class DefaultEventManager implements EventManager {
    
    ServiceManager manager;
    Logger log = null;
    AnnotationDB db = new AnnotationDB();
    Map<Operations, Set<Method>> methodsmap = new HashMap<Operations, Set<Method>>();
    Collection<Event> events = new ArrayList<Event>();
    private boolean started = false;

    @Inject
    public DefaultEventManager(Logger log, ServiceManager manager) {
        this.log = log;
        this.manager = manager;
        manager.registerOperations(this);
    }
    
    public String getName() {
        return EventManager.FQN;
    }

    public void start() {
        if (!started) {
            initListeners();
            this.started = true;
        }
    }

    public void stop() {
        if (started) {
            log.info("Event Manager stopped");
            this.started = false;
        }
    }
    
    public Collection<Operations> getListeners() {
        return this.methodsmap.keySet();
    }
    
    public void broadcast(Event e) {
        try {
            for(Operations s : this.methodsmap.keySet()) {
                log.info("dealing with operation {}",s.getName());
                
                for (Method m : this.methodsmap.get(s)) {
                    
                    log.info("dealing with method {}",m.getName());
                    
                    Class[] params = m.getParameterTypes();
                    
                    if (params.length == 0) {
                        log.debug("No params found");
                        m.invoke(s,new Object[]{});
                    } else if (params.length == 1) {
                        log.debug("param found");
                        m.invoke(s, new Object[]{e});
                    } else {
                        throw new IllegalArgumentException("");
                    }
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            log.error(ex.toString());
        }
        
        this.events.add(e);
    }
    
    
    public int getBroadcastEventCount() {
        return this.events.size();
    }
    
    private void initListeners() {
        try {
            // annotation database parameters

            db.setScanClassAnnotations(false);
            db.setScanFieldAnnotations(false);
            db.setScanMethodAnnotations(true);
            db.setScanParameterAnnotations(false);
            db.addIgnoredPackages("javax.*","org.*");
            
            URL[] urls = ClasspathUrlFinder.findClassPaths();
            
            db.scanArchives(urls);
            
            Map<String,Set<String>> annotatedClassesMap = db.getAnnotationIndex();
            
            Set<String> classes = annotatedClassesMap.get(OnEvent.class.getName());
            if (null != classes) { 
                for (String cl :  classes) {
                    Class clazz = Class.forName(cl);
                    Operations instance = getInstance(clazz);
                    if (null != instance) {
                        log.info("found operation {}", instance);
                        this.methodsmap.put(instance,getAnnotatedMethods(clazz, OnEvent.class));
                    }
                }
            }

        } catch (IOException ioe) {
            log.error(ioe.getMessage());
            
        } catch (ClassNotFoundException cl) {
            log.error(cl.getMessage());
        }
        
    }
    
    private Set<Method> getAnnotatedMethods(Class clazz, Class annotation) {
        Set<Method> methods = new HashSet<Method>();
        
        for(Method m: clazz.getMethods()) {
            if (null != m.getAnnotation(annotation)) {
                methods.add(m);
            }
        }
        
        return methods;
    }
    
    private Operations getInstance(Class clazz) {
            
        String fqn = null;
        for (Class cl : getInterfaces(clazz)) {
            for(Field f: cl.getDeclaredFields()) {
                try {
                   Operations s = this.manager.getOperation((String)f.get(null));
                   if (null != s) {
                       log.info("Retrieving operation {} from service manager",s);
                       return s;
                   } else {
                       log.info("Did not find registered instance for operation {}",f.get(null));    
                   }
                } catch (IllegalAccessException iae) {
                    log.error(iae.getMessage());
                }
            }
        }
        
        return null;
    }
    
    private Collection<Class> getInterfaces(Class in) {
        Collection<Class> res = new ArrayList<Class>();
        for(Class c : in.getInterfaces()) {
            res.add(c);
            res.addAll(getInterfaces(c));
        }
        return res;
    }

    public boolean isStarted() {
        return this.started;
    } 
    
}
